/*
 * gcc unix_sockets.c -o unix_sockets
 */
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <errno.h>

#define QLEN    10
#define STALE   30
#define CLI_PATH    "/tmp/"
#define CLI_PERM    S_IRWXU /* rwx for user only */

int server_listen(const char *name)
{
    int fd, err, rval;
    struct sockaddr_un un;
    if (strlen(name) >= sizeof(un.sun_path)) {
        errno = ENAMETOOLONG;
        return -1;
    }
    /* create a UNIX domain stream socket */
    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        return -2;
    }
    unlink(name); /* in case it already exists */
    /* fill in socket address structure */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, name);
    /* bind the name to the descriptor */
    if (bind(fd, (struct sockaddr *)&un, sizeof(struct sockaddr_un)) < 0) {
        rval = -3;
        goto errout;
    }
    if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */
        rval = -4;
        goto errout;
    }
    return fd;
errout:
    err = errno;
    close(fd);
    errno = err;
    return rval;
}

int server_accept(int listenfd, uid_t *uidptr)
{
    int clifd, err, rval;
    socklen_t len;
    time_t staletime;
    struct sockaddr_un un;
    struct stat statbuf;
    char *name;
    /* allocate enough space for longest name plus terminating null */
    if ((name = malloc(sizeof(un.sun_path + 1))) == NULL) {
        return -1;
    }
    len = sizeof(un);
    memset(&un, 0, len);
    if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
        free(name);
        return -2; /* often errno=EINTR, if signal caught */
    }
    /* obtain the client’s uid from its calling address */
    len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
    memcpy(name, un.sun_path, len);
    name[len] = 0; /* null terminate */
    if (stat(name, &statbuf) < 0) {
        rval = -3;
        goto errout;
    }
#ifdef S_ISSOCK /* not defined for SVR4 */
    if (S_ISSOCK(statbuf.st_mode) == 0) {
        rval = -4; /* not a socket */
        goto errout;
    }
#endif
    if ((statbuf.st_mode & (S_IRWXG | S_IRWXO))
        || (statbuf.st_mode & S_IRWXU) != S_IRWXU) {
        rval = -5; /* is not rwx------ */
        goto errout;
    }
    staletime = time(NULL) - STALE;
    if (statbuf.st_atime < staletime
        || statbuf.st_ctime < staletime
        || statbuf.st_mtime <staletime) {
        rval = -6; /* i-node is too old */
        goto errout;
    }
    if (uidptr != NULL) {
        *uidptr = statbuf.st_uid; /* return uid of caller */
    }
    unlink(name); /* we're done with pathname now */
    free(name);
    return clifd;
errout:
    err = errno;
    close(clifd);
    free(name);
    errno = err;
    return rval;
}

/*
 * @param name server name to connect
 * @return the connected socket or error(<0)
 */
int client_connect(const char *name) {
    int fd, len, err, rval;
    struct sockaddr_un un, sun;
    int do_unlink = 0;
    if (strlen(name) >= sizeof(un.sun_path)) {
        errno = ENAMETOOLONG;
        return -1;
    }
    /* create a UNIX domain stream socket */
    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
        return -1;
    }
    /* fill socket address structure with our address */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    sprintf(un.sun_path, "%s%05ld", CLI_PATH, (long)getpid());
    unlink(un.sun_path); /* in case it already exists */
    if (bind(fd, (struct sockaddr *)&un, sizeof(struct sockaddr_un)) < 0) {
        rval = -2;
        goto errout;
    }
    if (chmod(un.sun_path, CLI_PERM) < 0) {
        rval = -3;
        do_unlink = 1;
        goto errout;
    }
    /* fill socket address structure with server’s address */
    memset(&sun, 0, sizeof(sun));
    sun.sun_family = AF_UNIX;
    strcpy(sun.sun_path, name);
    if (connect(fd, (struct sockaddr *)&sun, sizeof(struct sockaddr_un)) < 0) {
        rval = -4;
        do_unlink = 1;
        goto errout;
    }
    return fd;
errout:
    err = errno;
    close(fd);
    if (do_unlink) {
        unlink(un.sun_path);
    }
    errno = err;
    return rval;
}

int main()
{
    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork");
        return -1;
    } else if (pid == 0) { /* child */
        int fd;
        const char hello[] = "hello from client";
        sleep(1);
        fd = client_connect("/tmp/myserver");
        if (fd > 0) {
            write(fd, hello, sizeof(hello));
            close(fd);
        }
    } else if (pid > 0) { /* parent */
        uid_t uid;
        int svrfd, clifd;
        char buff[128];
        svrfd = server_listen("/tmp/myserver");
        clifd = server_accept(svrfd, &uid);
        if (clifd > 0) {
            printf("got clifd=%d, uit=%u\n", clifd, uid);
            memset(buff, 0, sizeof(buff));
            read(clifd, buff, sizeof(buff));
            printf("got message: %s\n", buff);
            close(clifd);
        }
        close(svrfd);
    }
    return 0;
}
